﻿using Core;
using Core.Exceptions;
using Core.Extensions;
using Core.Helpers;
using Core.Migrations;
using Core.Models;
using Core.Persistence;
using Core.Tenant.Abstractions;
using Core.Tenant.Dtos;
using Core.Tenant.Features.CreateTenant;
using Core.Tenant.Features.PaginationQuery;
using Finbuckle.MultiTenant;
using Finbuckle.MultiTenant.Abstractions;
using FrameworkCore.Migrations;
using Gridify.EntityFramework;
using Infrastructure.Tenant.Persistence;
using Mapster;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Infrastructure.Tenant.Services;

public sealed class TenantService : ITenantService
{
    private readonly IMultiTenantStore<BoilerplateTenantInfo> _tenantStore;
    private readonly DatabaseOptions _config;
    private readonly IServiceProvider _serviceProvider;
    private readonly ILoggerFactory _logger;
    private readonly IBoilerplateDataProvider _dataProvider;
    private readonly TenantDbContext _context;

    public TenantService(
        IMultiTenantStore<BoilerplateTenantInfo> tenantStore,
        IOptions<DatabaseOptions> config,
        IServiceProvider serviceProvider,
        ILoggerFactory logger,
        IBoilerplateDataProvider dataProvider,
        TenantDbContext context)
    {
        _tenantStore = tenantStore;
        _config = config.Value;
        _serviceProvider = serviceProvider;
        _logger = logger;
        _dataProvider = dataProvider;
        _context = context;
    }

    public async Task<string> ActivateAsync(string id, CancellationToken cancellationToken)
    {
        var tenant = await GetTenantInfoAsync(id).ConfigureAwait(false);

        if (tenant.IsActive)
        {
            throw new BoilerplateException($"租户 {id} 已激活");
        }

        tenant.Activate();

        await _tenantStore.TryUpdateAsync(tenant).ConfigureAwait(false);

        return $"tenant {id} is now activated";
    }

    public async Task<string> CreateAsync(CreateTenantCommand request, CancellationToken cancellationToken)
    {
        var connectionString = request.ConnectionString;
        if (request.ConnectionString?.Trim() == _config.ConnectionString.Trim())
        {
            connectionString = string.Empty;
        }

        BoilerplateTenantInfo tenant = new(request.Id, request.Name, connectionString, request.AdminEmail, request.Issuer);
        await _tenantStore.TryAddAsync(tenant).ConfigureAwait(false);

        await InitializeDatabase(tenant).ConfigureAwait(false);

        return tenant.Id;
    }

    private async Task InitializeDatabase(BoilerplateTenantInfo tenant)
    {
        if (!string.IsNullOrWhiteSpace(tenant.ConnectionString))//创建数据库
        {
            var assemblies = AssemblyDiscoveryHelper.GetAllAssemblies(CommonHelper.DefaultFileProvider);
            MigrationExecutor.ExecuteMigrationsFromDlls(assemblies.Select(s => s.Key), tenant.ConnectionString, _config, _dataProvider, _logger);
        }

        // First create a new scope
        using var scope = _serviceProvider.CreateScope();

        // Then set current tenant so the right connection string is used
        scope.ServiceProvider.GetRequiredService<IMultiTenantContextSetter>()
            .MultiTenantContext = new MultiTenantContext<BoilerplateTenantInfo>()
            {
                TenantInfo = tenant
            };
        // using the scope, perform migrations / seeding
        var initializers = scope.ServiceProvider.GetServices<IDbInitializer>();
        foreach (var initializer in initializers)
        {
            await initializer.SeedAsync(CancellationToken.None).ConfigureAwait(false);
        }
    }

    public async Task<string> DeactivateAsync(string id)
    {
        var tenant = await GetTenantInfoAsync(id).ConfigureAwait(false);
        if (!tenant.IsActive)
        {
            throw new BoilerplateException($"tenant {id} is already deactivated");
        }

        tenant.Deactivate();
        await _tenantStore.TryUpdateAsync(tenant).ConfigureAwait(false);
        return $"tenant {id} is now deactivated";
    }

    public async Task<bool> ExistsWithIdAsync(string id) =>
        await _tenantStore.TryGetAsync(id).ConfigureAwait(false) is not null;

    public async Task<bool> ExistsWithNameAsync(string name) =>
        (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Any(t => t.Name == name);

    public async Task<List<TenantDetail>> GetAllAsync()
    {
        var tenants = (await _tenantStore.GetAllAsync().ConfigureAwait(false)).Adapt<List<TenantDetail>>();
        return tenants;
    }

    public async Task<PaginatedData<TenantDetail>> GetPageAsync(TenantsWithPaginationQuery request,CancellationToken cancellationToken)
    {
        return (await _context.TenantInfo
            .AsNoTracking()
            .GridifyAsync(request, cancellationToken))
            .ToPaginatedList<BoilerplateTenantInfo, TenantDetail>(request.Page, request.PageSize);
    }

    public async Task<TenantDetail> GetByIdAsync(string id) =>
        (await GetTenantInfoAsync(id).ConfigureAwait(false))
            .Adapt<TenantDetail>();

    public async Task<DateTimeOffset> UpgradeSubscription(string id, DateTimeOffset extendedExpiryDate)
    {
        var tenant = await GetTenantInfoAsync(id).ConfigureAwait(false);
        tenant.SetValidity(extendedExpiryDate);
        await _tenantStore.TryUpdateAsync(tenant).ConfigureAwait(false);
        return tenant.ValidUpto;
    }

    private async Task<BoilerplateTenantInfo> GetTenantInfoAsync(string id) =>
    await _tenantStore.TryGetAsync(id).ConfigureAwait(false)
        ?? throw new NotFoundException($"{typeof(BoilerplateTenantInfo).Name} {id} Not Found.");

    public async Task<TenantResponse> GetByNameAsync(string name) =>
        (await GetAllAsync().ConfigureAwait(false))
        .Where(t => t.Name == name)
        .FirstOrDefault()?
        .Adapt<TenantResponse>() ?? new TenantResponse();
}
